using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.IO;
using Umdf.ComInterop;
using System.Runtime.InteropServices;
using Umdf.ComInterop;
using System.Security.Principal;
using Umdf.SideShow.Events;
using Umdf.SideShow.Contents;

namespace Umdf.SideShow
{
    [ComVisible(true), Guid("DFB08ACD-4D9C-4324-830E-83E95F216CFD")]
    public abstract class SideShowDriver : ISideShowDriver, ISideShowDriverInitialize
    {
        private string _name;
        private string _manufacturer;
        private string _version;
        private DateTime _time;
        private bool _userStateAvailable;
        private string _shortTimeFormat;
        private string _longTimeFormat;
        private string _shortDateFormat;
        private string _longDateFormat;
        private string _language;
        private List<SideShowApplication> _applications;
        private List<Guid> _supportedEndpoints;
        private bool _notificationsEnabled;
        private IWDFNamedPropertyStore _propertyStore;
        private object _lockObject = new object();
        private SecurityIdentifier _user;
        private ISideShowDevice _device;

        protected SideShowDriver()
        {
            _applications = new List<SideShowApplication>();
            _user = new SecurityIdentifier(WellKnownSidType.InteractiveSid, null);
            InitializeMetaData();
        }

        private void InitializeMetaData()
        {
            if (this.GetType().IsDefined(typeof(SideShowDeviceMetadataAttribute), false))
            {
                SideShowDeviceMetadataAttribute attr = (SideShowDeviceMetadataAttribute)this.GetType().GetCustomAttributes(typeof(SideShowDeviceMetadataAttribute), false)[0];
                _name = attr.Name;
                _manufacturer = attr.Manufacturer;
                _version = attr.Version;
            }
            else
            {
                _name = "Unknown device";
                _manufacturer = "Unknown manufacturer";
                _version = "1.0";
            }
            _supportedEndpoints = new List<Guid>();
            if (this.GetType().IsDefined(typeof(SideShowSupportedEndpointAttribute), false))
            {
                try
                {
                    foreach (SideShowSupportedEndpointAttribute attr in this.GetType().GetCustomAttributes(typeof(SideShowSupportedEndpointAttribute), false))
                    {
                        _supportedEndpoints.Add(attr.EndpointId);
                    }
                }
                catch (Exception ex)
                {
                    int i = 0;
                }
            }
        }

        public string Name
        {
            get { return _name; }
        }

        public string Manufacturer
        {
            get { return _manufacturer; }
        }

        public string Version
        {
            get { return _version; }
        }

        public DateTime Time
        {
            get { return _time; }
            set { _time = value; }
        }

        public string ShortTimeFormat
        {
            get { return _shortDateFormat; }
            set { _shortDateFormat = value; }
        }

        public string LongTimeFormat
        {
            get { return _longTimeFormat; }
            set { _longTimeFormat = value; }
        }

        public string ShortDateFormat
        {
            get { return _shortDateFormat; }
            set { _shortDateFormat = value; }
        }

        public string LongDateFormat
        {
            get { return _longDateFormat; }
            set { _longDateFormat = value; }
        }

        public string Language
        {
            get { return _language; }
            set { _language = value; }
        }

        public bool UserStateAvailable
        {
            get { return _userStateAvailable; }
            set { _userStateAvailable = value; }
        }

        public List<Guid> SupportedEndpoints
        {
            get { return new List<Guid>(_supportedEndpoints); }
        }

        public IList<SideShowApplication> Applications
        {
            get { return _applications; }
        }

        /*
        internal Dictionary<Guid, SideShowEndpoint> CreateEndpoints()
        {
            Dictionary<Guid, SideShowEndpoint> endpoints = new Dictionary<Guid, SideShowEndpoint>();
            foreach (KeyValuePair<Guid, Type> entry in _supportedEndpoints)
            {
                SideShowEndpoint endpoint = (SideShowEndpoint)Activator.CreateInstance(entry.Value);
                endpoints.Add(entry.Key, endpoint);
            }
            return endpoints;
        }
        */

        protected virtual void OnAddApplication(SideShowApplication app)
        {
        }

        protected virtual void OnRemoveApplication(SideShowApplication app)
        {
        }

        protected virtual void OnAddContent(SideShowApplication application, SideShowEndpoint sideShowEndpoint, Content newContent)
        {
        }

        protected virtual void OnRemoveContent(SideShowApplication application, SideShowEndpoint sideShowEndpoint, Content newContent)
        {
        }

        protected virtual bool Initialize()
        {
            return true;
        }

        protected virtual void Deinitialize()
        {
        }

        protected virtual void OnError(Guid applicationId, Guid endpointId, uint contentId)
        {
        }

        public void PostEvent(SideShowEvent e)
        {
            _device.PostEvent(e);
        }

        #region ISideShowDriver Members

        void ISideShowDriver.OnGetDeviceName(out string ppwszName)
        {
            ppwszName = _name;
        }

        void ISideShowDriver.OnGetDeviceManufacturer(out string ppwszManufacturer)
        {
            ppwszManufacturer = _manufacturer;
        }

        void ISideShowDriver.OnGetDeviceFirmwareVersion(out string ppwszVersion)
        {
            ppwszVersion = _version;
        }

        void ISideShowDriver.OnSetTime(System.Runtime.InteropServices.ComTypes.FILETIME FileTime)
        {
            long fileTime = FileTime.dwHighDateTime << 32 + FileTime.dwLowDateTime;
            _time = DateTime.FromFileTime(fileTime);
        }

        void ISideShowDriver.OnSetTimeZone(ref SideShowTimeZoneInformation pTimeZoneInformation)
        {
            
        }

        void ISideShowDriver.OnSetCurrentUser(IntPtr pUserSid)
        {
            _user = new SecurityIdentifier(pUserSid);
        }

        void ISideShowDriver.OnGetCurrentUser(out IntPtr ppUserSid)
        {
            if (_user != null)
            {
                byte[] buf = new byte[_user.BinaryLength];
                _user.GetBinaryForm(buf, 0);
                ppUserSid = Marshal.AllocCoTaskMem(buf.Length);
                Marshal.Copy(buf, 0, ppUserSid, buf.Length);
            }
            else
            {
                ppUserSid = IntPtr.Zero;
            }
        }

        void ISideShowDriver.OnSetUserState(IntPtr pUserSid, UserState state)
        {
            _userStateAvailable = state == UserState.Available;
        }

        void ISideShowDriver.OnSetShortDateFormat(IntPtr pUserSid, string pwszDateFormat)
        {
            _shortDateFormat = pwszDateFormat;
        }

        void ISideShowDriver.OnSetLongDateFormat(IntPtr pUserSid, string pwszDateFormat)
        {
            _longDateFormat = pwszDateFormat;
        }

        void ISideShowDriver.OnSetShortTimeFormat(IntPtr pUserSid, string pwszTimeFormat)
        {
            _shortTimeFormat = pwszTimeFormat;
        }

        void ISideShowDriver.OnSetLongTimeFormat(IntPtr pUserSid, string pwszTimeFormat)
        {
            _longTimeFormat = pwszTimeFormat;
        }

        void ISideShowDriver.OnSetLanguage(IntPtr pUserSid, string pwszLanguage)
        {
            _language = pwszLanguage;
        }

        void ISideShowDriver.OnGetDeviceEndpoints(out IntPtr rgEndpoints, out uint pcEndpoints)
        {
            Guid[] keys = _supportedEndpoints.ToArray(); // new List<Guid>(_supportedEndpoints.Keys).ToArray();
            rgEndpoints = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(Guid)) * _supportedEndpoints.Count);
            IntPtr t = rgEndpoints;
            for (int i = 0; i < _supportedEndpoints.Count; i++)
            {
                Marshal.StructureToPtr(keys[i], t, false);
                t = (IntPtr)(t.ToInt32() + Marshal.SizeOf(typeof(Guid)));
            }
            pcEndpoints = (uint)_supportedEndpoints.Count;
        }

        void ISideShowDriver.OnGetDeviceCapabilities(ref PropertyKey pKey, out PropVariant pvValue)
        {
            pvValue = new PropVariant();
            pvValue.vt = (ushort)VarEnum.VT_NULL;
        }

        void ISideShowDriver.OnGetPreEnabledApplications(out IntPtr ppApplicationIds, out IntPtr ppEndpointIds, out uint pcApplications)
        {
            ppApplicationIds = IntPtr.Zero;
            ppEndpointIds = IntPtr.Zero;
            pcApplications = 0;
        }

        void ISideShowDriver.OnSetApplicationOrder(Guid[] pApplicationIds, uint cApplicationIds)
        {
            lock (_lockObject)
            {
                List<SideShowApplication> newList = new List<SideShowApplication>(pApplicationIds.Length);
                foreach (Guid g in pApplicationIds)
                {
                    SideShowApplication app = _applications.Find(delegate(SideShowApplication a)
                    {
                        return a.Id == g;
                    });
                    if (app != null)
                    {
                        newList.Add(app);
                    }
                }
                _applications.Clear();
                _applications = newList;
            }
        }

        void ISideShowDriver.OnGetApplicationOrder(out IntPtr ppApplicationIds, out uint pcApplicationIds)
        {
            ppApplicationIds = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(Guid)) * _applications.Count);
            IntPtr t = ppApplicationIds;
            for (int i = 0; i < _applications.Count; i++)
            {
                Marshal.StructureToPtr(_applications[i], t, false);
                t = (IntPtr)(t.ToInt32() + Marshal.SizeOf(typeof(Guid)));
            }
            pcApplicationIds = (uint)_applications.Count;
        }

        void ISideShowDriver.OnAddApplication(IntPtr pUserSid, ref Guid ApplicationId, ref Guid EndPointId, string pcwszName, uint dwCachePolicy, uint dwOnlineOnly, byte[] pbLargeIcon, uint cbLargeIcon, byte[] pbMediumIcon, uint cbMediumIcon, byte[] pbSmallIcon, uint cbSmallIcon)
        {
            lock (_lockObject)
            {
                Guid appId = ApplicationId;
                if (_applications.Exists(delegate(SideShowApplication a)
                {
                    return a.Id == appId;
                }))
                {
                    return;
                }
                Image smallImage = null;
                Image mediumImage = null;
                Image largeImage = null;
                if (pbSmallIcon != null && cbSmallIcon > 0)
                {
                    using (MemoryStream ms = new MemoryStream(pbSmallIcon))
                    {
                        smallImage = new Bitmap(ms);
                    }
                }
                if (pbMediumIcon != null && cbMediumIcon > 0)
                {
                    using (MemoryStream ms = new MemoryStream(pbMediumIcon))
                    {
                        mediumImage = new Bitmap(ms);
                    }
                }
                if (pbLargeIcon != null && cbLargeIcon > 0)
                {
                    using (MemoryStream ms = new MemoryStream(pbLargeIcon))
                    {
                        largeImage = new Bitmap(ms);
                    }
                }
                SideShowApplication app = new SideShowApplication(
                    this,
                    ApplicationId,
                    pcwszName, (CachePolicy)dwCachePolicy,
                    dwOnlineOnly == 1,
                    smallImage,
                    mediumImage,
                    largeImage);

                _applications.Add(app);
                OnAddApplication(app);

#if DEBUG
                string dir = string.Format("c:\\SideShowDebug\\{0}", ApplicationId);
                if (!Directory.Exists(dir))
                {
                    Directory.CreateDirectory(dir);
                }
                ImageContent ic = new ImageContent(0, smallImage);
                ic.Save(dir);
                ic = new ImageContent(1, mediumImage);
                ic.Save(dir);
                ic = new ImageContent(2, largeImage);
                ic.Save(dir);
#endif
            }
        }

        void ISideShowDriver.OnRemoveApplication(IntPtr pUserSid, ref Guid ApplicationId)
        {
            lock (_lockObject)
            {
                Guid appId = ApplicationId;
                SideShowApplication application = _applications.Find(delegate(SideShowApplication app)
                {
                    return app.Id == appId;
                });
                if (application != null)
                {
                    _applications.Remove(application);
                    OnRemoveApplication(application);
                }
            }
        }

        void ISideShowDriver.OnRemoveAllApplications(IntPtr pUserSid)
        {
            lock (_lockObject)
            {
                _applications.Clear();
            }
        }

        void ISideShowDriver.OnAddContent(IntPtr pUserSid, ref Guid ApplicationId, ref Guid EndPointId, uint ContentId, IntPtr pData, uint cbData)
        {
            lock (_lockObject)
            {
                try
                {
                    Guid appId = ApplicationId;
                    SideShowApplication application = _applications.Find(delegate(SideShowApplication app)
                    {
                        return app.Id == appId;
                    });
                    if (application != null)
                    {
                        byte[] data = new byte[cbData];
                        Marshal.Copy(pData, data, 0, (int)cbData);
                        Content newContent = ContentFactory.Create(ContentId, data);
                        application.Endpoints[EndPointId].AddContent(newContent);
#if DEBUG
                        string dir = string.Format("c:\\SideShowDebug\\{0}\\{1}", ApplicationId, EndPointId);
                        if (!Directory.Exists(dir))
                        {
                            Directory.CreateDirectory(dir);
                        }
                        newContent.Save(dir);

#endif
                        OnAddContent(application, application.Endpoints[EndPointId], newContent);
                    }
                }
                catch (Exception ex)
                {
                    OnError(ApplicationId, EndPointId, ContentId);
                }
                
            }
        }

        void ISideShowDriver.OnRemoveContent(IntPtr pUserSid, ref Guid ApplicationId, ref Guid EndPointId, uint ContentId)
        {
            lock (_lockObject)
            {
                Guid appId = ApplicationId;
                SideShowApplication application = _applications.Find(delegate(SideShowApplication app)
                {
                    return app.Id == appId;
                });
                if (application != null)
                {
                    Content content = application.Endpoints[EndPointId].GetContent(ContentId);
                    application.Endpoints[EndPointId].RemoveContent(ContentId);
                    OnRemoveContent(application, application.Endpoints[EndPointId], content);
                }
            }
        }

        void ISideShowDriver.OnRemoveAllContent(IntPtr pUserSid, ref Guid ApplicationId, ref Guid EndPointId)
        {
            lock (_lockObject)
            {
                Guid appId = ApplicationId;
                SideShowApplication application = _applications.Find(delegate(SideShowApplication app)
                {
                    return app.Id == appId;
                });
                if (application != null)
                {
                    application.Endpoints[EndPointId].RemoveAllContent();
                }
            }
        }

        void ISideShowDriver.OnAddNotification(IntPtr pUserSid, ref Guid ApplicationId, uint NotificationId, System.Runtime.InteropServices.ComTypes.FILETIME ftExpiration, string pcwszTitle, string pcwszMessage, byte[] pbImage, uint cbImage)
        {
            if(!_notificationsEnabled)
            {
                return;
            }

            lock (_lockObject)
            {
                Guid appId = ApplicationId;
                SideShowApplication application = _applications.Find(delegate(SideShowApplication app)
                {
                    return app.Id == appId;
                });
                if (application != null)
                {
                    Image image = null;
                    if(pbImage != null && cbImage > 0)
                    {
                        using(MemoryStream ms = new MemoryStream(pbImage))
                        {
                            image = new Bitmap(ms);
                        }
                    }
                    long fileTime = ftExpiration.dwHighDateTime << 32 + ftExpiration.dwLowDateTime;
                    DateTime expirationTime = DateTime.FromFileTime(fileTime);
                    application.AddNotification(new SideShowNotification(
                        (int)NotificationId, 
                        expirationTime, 
                        pcwszTitle, 
                        pcwszMessage, 
                        image));
                }
            }
        }

        void ISideShowDriver.OnRemoveNotification(IntPtr pUserSid, ref Guid ApplicationId, uint NotificationId)
        {
            if (!_notificationsEnabled)
            {
                return;
            }

            lock (_lockObject)
            {
                Guid appId = ApplicationId;
                SideShowApplication application = _applications.Find(delegate(SideShowApplication app)
                {
                    return app.Id == appId;
                });
                if (application != null)
                {
                    application.RemoveNotification((int)NotificationId);
                }
            }
        }

        void ISideShowDriver.OnRemoveAllNotifications(IntPtr pUserSid, ref Guid ApplicationId)
        {
            if (!_notificationsEnabled)
            {
                return;
            }

            lock (_lockObject)
            {
                Guid appId = ApplicationId;
                SideShowApplication application = _applications.Find(delegate(SideShowApplication app)
                {
                    return app.Id == appId;
                });
                if (application != null)
                {
                    application.RemoveAllNotifications();
                }
            }
        }

        void ISideShowDriver.OnSetNotificationsEnabled(IntPtr pUserSid, int fIsEnabled)
        {
            _notificationsEnabled = fIsEnabled == 1;
        }

        void ISideShowDriver.OnProcessWpdMessage(object pPortableDeviceValuesParams, object pPortableDeviceValuesResults)
        {
            
        }

        #endregion

        #region ISideShowDriverInitialize interface

        bool ISideShowDriverInitialize.Initialize(ISideShowDevice device, IWDFNamedPropertyStore pStore)
        {
            _device = device;
            if (pStore != null)
            {
                _propertyStore = pStore;
            }
            else
            {
                Marshal.ThrowExceptionForHR(ComHResults.E_INVALIDARG);
            }
            return Initialize();
        }

        void ISideShowDriverInitialize.Deinitialize()
        {
            this.Deinitialize();
        }


        #endregion
    }
}
